package com.zodiac.app.szse.parse;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import lombok.Builder;
import lombok.Data;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Element;
import org.jsoup.nodes.Node;
import org.jsoup.select.Elements;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

/**
 * TODO 处理断页的行, 根据列的宽度来识别是否是同一列
 * HTML解析
 */
public class HtmlParser {

    private String html;
    private List<Part> parts;

    public static HtmlParser builder(File htmlOfPdf) {
        try {
            return builder(new String(Files.readAllBytes(htmlOfPdf.toPath()), StandardCharsets.UTF_8));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static HtmlParser builder(String htmlOfPdf) {
        final HtmlParser parser = new HtmlParser();
        parser.html = htmlOfPdf;
        return parser;
    }

    // 解析
    public List<Part> parse() {
        try {
            // 过滤出所有数据节点（分为：文本节点和表格节点）
            extractDataNodes();
            // 数据节点处理
            processDataNodes();
            return this.parts;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 抽取数据节点，排除干扰节点
     *
     * @return
     */
    private HtmlParser extractDataNodes() {
        // 获取页眉+页脚所占用的文本行数
        String dataNodeCls = "div#page-container>div.pf>div.pc";
        final Elements pages = Jsoup.parse(this.html).select(dataNodeCls);
        // 删除页眉和页脚
        removeHeaderAndFooterLines(pages);
        // 获取所有文本行和单元格
        final List<Element> allTextsWithCells = pages.stream().map(page -> page.select(">div.t,div.c")).flatMap(Collection::stream).collect(Collectors.toList());
        // 过滤掉空行
        final List<Element> nonEmptyTextsWithCells = allTextsWithCells.stream().filter(element -> !(element.hasClass("t") && element.text().trim().isEmpty())).collect(Collectors.toList());

        // 对文本与表格进行匹配，成对出现
        final List<Part> parts = new ArrayList<>();
        this.parts = parts;
        Part part = new Part();
        parts.add(part);
        final int size = nonEmptyTextsWithCells.size();
        for (int i = 0; i < size; i++) {
            final Element element = nonEmptyTextsWithCells.get(i);
            final String className = element.className();
            final String[] classNames = className.split("\\s+");
            final String value = element.text().trim();
            final String pageNo = element.parent().parent().attr("data-page-no");

            if (element.hasClass("c")) {
                part.getCells().add(Cell.builder().pageNo(pageNo).cls(className).position(classNames[1]).value(value).build());
                // 判断是否进入下一次匹配
                if ((i < size - 1) && nonEmptyTextsWithCells.get(i + 1).hasClass("t")) {
                    part = new Part();
                    parts.add(part);
                }
                continue;
            }

            if (element.hasClass("t")) {
                part.getTextLines().add(TextLine.builder().pageNo(pageNo).cls(className).position(classNames[2]).value(value).build());
            }
        }
        return this;
    }

    /**
     * 删除页眉与页脚行
     */
    private void removeHeaderAndFooterLines(Elements pages) {
        // 删除 img 元素
        pages.stream().map(page -> page.select(">img")).filter(img -> !img.isEmpty()).forEach(Elements::remove);

        // 每页开头的文本行
        final Map<Element, Elements> pageTextsMap = Maps.newLinkedHashMap();
        // 每页开头单元格下的文本行
        final Map<Element, Elements> pageCellTextsMap = Maps.newLinkedHashMap();
        for (Element page : pages) {
            // 文本行
            final Elements texts = page.select(">div.t");
            if (!texts.isEmpty()) {
                pageTextsMap.put(page, texts);
            }
            // 单元格行
            final Element firstCell = page.selectFirst(">div.c");
            if (Objects.isNull(firstCell)) {
                continue;
            }
            final Elements cellTexts = firstCell.select(">div.t");
            if (cellTexts.size() > 1) {
                pageCellTextsMap.put(page, cellTexts);
            }
        }

        if (!pageTextsMap.isEmpty()) {
            // 获取页眉和页脚
            final List<Element> headerAndFooter = findHeaderAndFooter(pageTextsMap);
            if (!headerAndFooter.isEmpty()) {
                // 删除页面下的页眉和页脚
                pages.forEach(page -> page.select(">div.t").stream().filter(headerAndFooter::contains).forEach(Node::remove));
            }
        }

        if (!pageCellTextsMap.isEmpty()) {
            // 获取页眉和页脚
            final List<Element> headerAndFooter = findHeaderAndFooter(pageCellTextsMap);
            if (!headerAndFooter.isEmpty()) {
                for (Element page : pages) {
                    final Elements cells = page.select(">div.c");
                    if (cells.isEmpty()) {
                        continue;
                    }

                    // 第一个单元格
                    final Element first = cells.get(0);
                    // 将第一个单元格中的页眉和页脚删除
                    first.select(">div.t").stream().filter(headerAndFooter::contains).forEach(Node::remove);
                    final List<Element> filtered = new ArrayList<>(first.children());

                    // 找到与第一个单元格class一致的单元，将单元格内的text文本行放到外面来
                    final String cls = first.attr("class");
                    final int size = cells.size();
                    for (int i = 1; i < size; i++) {
                        final Element cell = cells.get(i);
                        if (Objects.equals(cls, cell.attr("class"))) {
                            filtered.addAll(cell.children());
                            continue;
                        }
                        filtered.add(cell);
                    }

                    page.children().forEach(Node::remove);
                    page.insertChildren(0, filtered);

                }
            }
        }
    }

    private List<Element> findHeaderAndFooter(Map<Element, Elements> pageTextsMap) {
        final List<Element> headerAndFooter = new ArrayList<>();
        final AtomicInteger pageNoCounter = new AtomicInteger(1);
        for (Map.Entry<Element, Elements> entry : pageTextsMap.entrySet()) {
            final Elements pageFirstCellTexts = entry.getValue();
            final int length = Math.min(3, pageFirstCellTexts.size());
            for (int i = 0; i < length; i++) {
                final Element e = pageFirstCellTexts.get(i);
                final String text = e.text().trim().replaceAll("\\s+", "");
                // 页眉：深圳中国农大科技股份有限公司 2019 年第三季度报告全文
                if (text.matches(".+\\d{4}年.+报告(全文)?")) {
                    headerAndFooter.add(e);
                    continue;
                }
                // 页脚(页码)：1, 2, 3 ...
                if (text.matches("\\d+") && text.length() < 5 && Integer.valueOf(text) == pageNoCounter.get()) {
                    pageNoCounter.incrementAndGet();
                    headerAndFooter.add(e);
                    continue;
                }
                // 页脚(页码)：1/20, 2/20, 3/20 ...
                if (text.matches("\\d+/\\d+") && text.length() < 5 && Integer.valueOf(text.split("/")[0]) == pageNoCounter.get()) {
                    pageNoCounter.incrementAndGet();
                    headerAndFooter.add(e);
                }
            }
        }
        return headerAndFooter;
    }

    /**
     * 处理抽取出来的数据节点，进行归整，以方便进一步提取
     *
     * @return
     */
    private HtmlParser processDataNodes() {
        for (Part part : this.parts) {
            part.getHtmlTags().addAll(part.getTextLines().stream().map(t -> "<div class='" + t.getCls() + "' page-no='" + t.getPageNo() + "'>" + t.getValue() + "</div>").collect(Collectors.toList()));
            part.setTexts(part.getTextLines().stream().map(TextLine::getValue).collect(Collectors.toList()));

            final List<Cell> cells = part.getCells();
            if (cells.isEmpty()) {
                continue;
            }
            // 将所有的数据行包装成html标签，方便后续进一步发现其中的规律，以便进一步提取数据
            part.getHtmlTags().addAll(cells.stream().map(c -> "<div class='" + c.getCls() + "' page-no='" + c.getPageNo() + "'>" + c.getValue() + "</div>").collect(Collectors.toList()));
            // 处理行
            final List<Row> rows = processRows(cellsToRows(cells));
            // 提取每行的值
            final List<List<String>> rowList = rows.stream().map(row -> row.getCells().stream().map(Cell::getValue).collect(Collectors.toList())).collect(Collectors.toList());
            // 删除空行和空列
            part.setRows(removeEmptyColumns(removeEmptyRows(rowList)));
        }
        return this;
    }

    /**
     * 将单元格分成多行
     *
     * @param cells
     */
    private List<Row> cellsToRows(List<Cell> cells) {
        // 每一行开头的位置，以此作为分行标识
        final String startPosition = cells.get(0).getPosition();
        final List<Row> rows = new ArrayList<>();
        Row row = null;
        for (Cell cell : cells) {
            if (Objects.equals(startPosition, cell.getPosition())) {
                row = new Row();
                rows.add(row);
            }
            row.getCells().add(cell);
        }
        return rows;
    }

    /**
     * 处理行
     *
     * @param rows
     * @return
     */
    private List<Row> processRows(List<Row> rows) {
        final List<Row> processedRows = new ArrayList<>();
        // 1. 跨行处理 -> 2. 断页行处理 -> 3. 跨列处理
        rows.forEach(row -> processedRows.addAll(processSpanRows(row).stream().map(this::processBrokenPageRow).map(this::processSpanColumns).collect(Collectors.toList())));
        return processedRows;
    }

    /**
     * 跨行处理
     *
     * @param row
     * @return
     */
    private List<Row> processSpanRows(Row row) {
        final List<Cell> cells = row.getCells();
        if (cells.size() <= 1) {
            return Lists.newArrayList(row);
        }
        final List<Row> spanRows = cellsToRows(cells.subList(1, cells.size()));
        if (spanRows.size() <= 1) {
            return Lists.newArrayList(row);
        }
        final Cell firstCell = cells.get(0);
        spanRows.forEach(spanRow -> {
            processBrokenPageRow(spanRow);
            spanRow.getCells().add(0, Cell.builder().pageNo(firstCell.getPageNo()).cls(firstCell.getCls()).position(firstCell.getPosition()).value(firstCell.getValue()).build());
        });
        return spanRows;
    }

    /**
     * 跨列处理，删除父列，保留子列，只能处理正规的跨列（正规的跨列保证父列与第一个子列对其）
     *
     * @param row
     * @return
     */
    private Row processSpanColumns(Row row) {
        final List<Cell> cells = row.getCells();
        if (cells.size() <= 1) {
            return row;
        }
        final String startPosition = row.getCells().get(0).getPosition();
        final Map<String, List<Cell>> groupByPosition = cells.stream().collect(Collectors.groupingBy(Cell::getPosition));
        for (Map.Entry<String, List<Cell>> entry : groupByPosition.entrySet()) {
            final String key = entry.getKey();
            final List<Cell> value = entry.getValue();
            // 如果存在跨列，则删除父列，保留子列
            if (!Objects.equals(startPosition, key) && value.size() > 1) {
                value.subList(0, value.size() - 1).forEach(cells::remove);
            }
        }
        return row;
    }

    /**
     * 断页行处理，将断列到下一页的单元格内容合并到上一页对应的单元格中
     *
     * @param row
     * @return
     */
    private Row processBrokenPageRow(Row row) {
        // 判断单元格数量
        final List<Cell> cells = row.getCells();
        if (cells.size() <= 1) {
            return row;
        }
        // 判断是否分页
        if (cells.stream().map(Cell::getPageNo).distinct().count() <= 1) {
            return row;
        }
        final Map<String, List<Cell>> groupByPosition = cells.stream().collect(Collectors.groupingBy(Cell::getPosition));
        for (Map.Entry<String, List<Cell>> entry : groupByPosition.entrySet()) {
            final List<Cell> value = entry.getValue();
            if (value.size() > 1) {
                value.get(0).setValue(value.stream().map(Cell::getValue).collect(Collectors.joining()));
                value.subList(1, value.size()).forEach(cells::remove);
            }
        }

        return row;
    }

    private static List<List<String>> removeEmptyRows(List<List<String>> rows) {
        return rows.stream().filter(row -> !String.join("", row).isEmpty()).collect(Collectors.toList());
    }

    private static List<List<String>> removeEmptyColumns(List<List<String>> rows) {
        // 行转列
        final TreeMap<Integer, List<String>> rowToColumns = Maps.newTreeMap();
        for (List<String> row : rows) {
            final int numberOfColumn = row.size();
            for (int i = 0; i < numberOfColumn; i++) {
                if (!rowToColumns.containsKey(i)) {
                    rowToColumns.put(i, new ArrayList<>());
                }
                rowToColumns.get(i).add(row.get(i));
            }
        }
        // 找出为空的列
        final List<Integer> emptyColumnNos = rowToColumns.keySet().stream().filter(k -> String.join("", rowToColumns.get(k)).isEmpty()).collect(Collectors.toList());
        if (emptyColumnNos.isEmpty()) {
            return rows;
        }
        return rows.stream().map(row -> IntStream.range(0, row.size()).filter(n -> !emptyColumnNos.contains(n)).mapToObj(row::get).collect(Collectors.toList())).collect(Collectors.toList());
    }

    @Data
    @Builder
    public static class TextLine {
        private String value;
        private String cls;
        private String position;
        private String pageNo;
    }

    @Data
    @Builder
    public static class Cell {
        private String value;
        private String cls;
        private String position;
        private String pageNo;
    }

    @Data
    public static class Row {
        private List<Cell> cells = new ArrayList<>();
    }

    @Data
    public static class Part {
        private List<TextLine> textLines = new ArrayList<>();
        private List<Cell> cells = new ArrayList<>();
        // 保留HTML标签的格式，以方便后续进一步细化HTML解析的规则
        private List<String> htmlTags = new ArrayList<>();
        // 解析后的文本数据
        private List<String> texts;
        // 解析后的表格数据
        private List<List<String>> rows;
    }
}
